// XUnzip.cpp  Version 1.3
//
// Authors:      Mark Adler et al. (see below)
//
// Modified by:  Lucian Wischik
//               lu@wischik.com
//
// Version 1.0   - Turned C files into just a single CPP file
//               - Made them compile cleanly as C++ files
//               - Gave them simpler APIs
//               - Added the ability to zip/unzip directly in memory without 
//                 any intermediate files
// 
// Modified by:  Hans Dietrich
//               hdietrich@gmail.com
//
// Version 1.3:  - Corrected size bug introduced by 1.2
//
// Version 1.2:  - Many bug fixes.  See CodeProject article for list.
//
// Version 1.1:  - Added Unicode support to CreateZip() and ZipAdd()
//               - Changed file names to avoid conflicts with Lucian's files
//

#define STRICT
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <time.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <tchar.h>
#include <zlib/minizip/unzip.h>

#if _MSC_VER <= 1200
#define __time32_t time_t
#endif //_MSC_VER

#include "XUnzip.h"

#define ZR_OK         0x00000000     // nb.
#define ZR_NOFILE     0x00000200     // couldn't create/open the file
#define ZR_WRITE      0x00000400     // a general error writing to the file
#define ZR_NOTFOUND   0x00000500     // couldn't find that file in the zip
#define ZR_ARGS       0x00010000     // general mistake with the arguments
#define ZR_NOTINITED  0x01000000     // initialisation didn't work

#pragma warning(disable : 4702)   // unreachable code

// case sensitivity when searching for filenames
#define CASE_SENSITIVE 1
#define CASE_INSENSITIVE 2

FILETIME timet2filetime(time_t timer)
{ 
	struct tm *tm = gmtime(&timer);
	SYSTEMTIME st;
	st.wYear = (WORD)(tm->tm_year+1900);
	st.wMonth = (WORD)(tm->tm_mon+1);
	st.wDay = (WORD)(tm->tm_mday);
	st.wHour = (WORD)(tm->tm_hour);
	st.wMinute = (WORD)(tm->tm_min);
	st.wSecond = (WORD)(tm->tm_sec);
	st.wMilliseconds=0;
	FILETIME ft;
	SystemTimeToFileTime(&st,&ft);
	return ft;
}

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
class TUnzip
{ public:
  TUnzip() : uf(0), m_bCurr(false) {}

	unzFile uf;
	bool m_bCurr;
  TCHAR rootdir[MAX_PATH];

  ZRESULT Open(void *z,unsigned int len,DWORD flags);
  ZRESULT Get(WIN32_FIND_DATA *ze);
  ZRESULT Find(const TCHAR *name,bool ic,WIN32_FIND_DATA *ze);
  ZRESULT Unzip(void *dst,unsigned int len,DWORD flags);
  ZRESULT Close();
};

ZRESULT TUnzip::Open(void* z, unsigned int len, DWORD flags)
{
	if (uf != 0)
		return ZR_NOTINITED;
	GetCurrentDirectory(MAX_PATH, rootdir);
	_tcscat(rootdir, _T("\\"));
	switch (flags)
	{
	case ZIP_FILENAME:
		uf = unzOpen((const TCHAR*)z);
		if (NULL == uf)
		{
			return ZR_NOFILE;
		}
		return ZR_OK;

	case ZIP_MEMORY:
		uf = unzOpen((const TCHAR*)z);
		if (NULL == uf)
		{
			return ZR_NOFILE;
		}		
		return ZR_OK;
	}
	return ZR_NOFILE;
}

ZRESULT TUnzip::Get(WIN32_FIND_DATA *ze)
{
	if (m_bCurr)
	{
		m_bCurr = false;
		unzCloseCurrentFile(uf);
	}
	ZeroMemory(ze, sizeof(*ze));
  unz_file_info ufi; 
  char fn[MAX_PATH];
	char extra[1024];
	size_t extralen = sizeof(extra);
  unzGetCurrentFileInfo(uf,&ufi,fn,MAX_PATH, extra, extralen,NULL,0);
	if (ufi.size_file_extra < extralen)
	{
		extralen = ufi.size_file_extra;
	}

  strcpy(ze->cFileName,fn); 
  ze->dwFileAttributes = ufi.external_fa;
	ze->nFileSizeLow     = ufi.uncompressed_size;
	//ze->ftCreationTime = ;
	//ze->ftLastAccessTime = ;
	//ze->ftLastWriteTime = ;
  //
  WORD dostime = (WORD)(ufi.dosDate&0xFFFF);
  WORD dosdate = (WORD)((ufi.dosDate>>16)&0xFFFF);
  FILETIME ft;
  DosDateTimeToFileTime(dosdate,dostime,&ft);
  ze->ftLastAccessTime =ft; ze->ftCreationTime =ft; ze->ftLastWriteTime =ft;
  // the zip will always have at least that dostime. But if it also has
  // an extra header, then we'll instead get the info from that.
  unsigned int epos=0;
  while (epos+4<extralen)
  { char etype[3]; etype[0]=extra[epos+0]; etype[1]=extra[epos+1]; etype[2]=0;
    int size = extra[epos+2];
    if (strcmp(etype,"UT")!=0) {epos += 4+size; continue;}
    int flags = extra[epos+4];
    bool hasmtime = (flags&1)!=0;
    bool hasatime = (flags&2)!=0;
    bool hasctime = (flags&4)!=0;
    epos+=5;
    if (hasmtime)
    { time_t mtime = *(__time32_t*)(extra+epos); epos+=4;
      ze->ftLastWriteTime = timet2filetime(mtime);
    }
    if (hasatime)
    { time_t atime = *(__time32_t*)(extra+epos); epos+=4;
      ze->ftLastAccessTime = timet2filetime(atime);
    }
    if (hasctime)
    { time_t ctime = *(__time32_t*)(extra+epos); 
      ze->ftCreationTime = timet2filetime(ctime);
    }
    break;
  }
  return ZR_OK;
}

ZRESULT TUnzip::Find(const TCHAR *name, bool ic, WIN32_FIND_DATA *ze)
{ 
	if (m_bCurr)
	{
		m_bCurr = false;
		unzCloseCurrentFile(uf);
	}
	int res = unzLocateFile(uf,name,ic?CASE_INSENSITIVE:CASE_SENSITIVE);
	if (res!=UNZ_OK)
	{ 
		if (ze!=NULL) 
		{
			ZeroMemory(ze,sizeof(WIN32_FIND_DATA));
		}
		return ZR_NOTFOUND;
	}
	if (ze!=NULL)
	{ 
		ZRESULT zres = Get(ze);
		if (zres!=ZR_OK) 
			return zres;
	}
	return ZR_OK;
}

void EnsureDirectory(const TCHAR *rootdir, const TCHAR *dir)
{ 
	if (dir==NULL || dir[0] == _T('\0')) 
		return;
	const TCHAR *lastslash = dir, *c = lastslash;
	while (*c != _T('\0')) 
	{
		if (*c==_T('/') || *c==_T('\\')) 
		lastslash=c; 
		c++;
	}
	const TCHAR *name=lastslash;
	if (lastslash!=dir)
	{ 
		TCHAR tmp[MAX_PATH]; 
		_tcsncpy(tmp, dir, lastslash-dir);
		tmp[lastslash-dir] = _T('\0');
		EnsureDirectory(rootdir,tmp);
		name++;
	}
	TCHAR cd[MAX_PATH]; 
	_tcscpy(cd,rootdir); 
	//_tcscat(cd,name);
	_tcscat(cd,dir);	//+++1.2
	CreateDirectory(cd,NULL);
}

ZRESULT TUnzip::Unzip(void *dst,unsigned int len,DWORD flags)
{ 
	// otherwise we're writing to a handle or a file
	if (m_bCurr)
	{
		m_bCurr = false;
		unzCloseCurrentFile(uf);
	}
	WIN32_FIND_DATA ze; 
	Get(&ze);
	// zipentry=directory is handled specially
	if ((ze.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0)
	{ 
#ifdef _UNICODE
		TCHAR uname[MAX_PATH];
		GetUnicodeFileName(ze.name, uname, MAX_PATH-1);
		EnsureDirectory(rootdir, uname);
#else
		EnsureDirectory(rootdir, ze.cFileName);
#endif
		return ZR_OK;
	}

	// otherwise, we write the zipentry to a file/handle
	HANDLE h;
	{ 
		const TCHAR *name = (const TCHAR *)dst;
		const TCHAR *c = name;
		while (*c) 
		{
			if (*c == _T('/') || *c == _T('\\')) 
				name = c + 1; 
			c++;
		}
		// if it's a relative filename, ensure directories. We do this as a service  
		// to the caller so they can just unzip straight unto ze.name.
		if (name != (const TCHAR *)dst)
		{ 
			TCHAR dir[MAX_PATH]; 
			_tcscpy(dir,(const TCHAR*)dst); 
			dir[name-(const TCHAR*)dst-1] = _T('\0');
			bool isabsolute = (dir[0]==_T('/') || dir[0]==_T('\\') || dir[1]==_T(':'));
			isabsolute |= (_tcsstr(dir,_T("../"))!=0) | (_tcsstr(dir,_T("..\\"))!=0);
			if (!isabsolute) 
				EnsureDirectory(rootdir,dir);
		}
		h = ::CreateFile((const TCHAR*)dst, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 
				ze.dwFileAttributes, NULL);
	}

	if (h == INVALID_HANDLE_VALUE)  
		return ZR_NOFILE;
	
	unzOpenCurrentFile(uf);
	BYTE buf[16384];  
	bool haderr=false;
	
	for (;;)
	{ 
		int res = unzReadCurrentFile(uf,buf,16384);
		if (res<0) 
		{
			haderr=true; 
			break;
		}
		if (res==0) 
			break;
		DWORD writ; 
		BOOL bres = WriteFile(h,buf,res,&writ,NULL);
		if (!bres) 
		{
			haderr=true; 
			break;
		}
	}
		SetFileTime(h,&ze.ftCreationTime,&ze.ftLastAccessTime,&ze.ftLastWriteTime);
		CloseHandle(h);
	unzCloseCurrentFile(uf);
	if (haderr) 
		return ZR_WRITE;
	return ZR_OK;
}

ZRESULT TUnzip::Close()
{
	if (m_bCurr)
	{
		m_bCurr = false;
		unzCloseCurrentFile(uf);
	}
	if (uf!=0) unzClose(uf); uf=0;
  return ZR_OK;
}

HZIP OpenZip(void *z,unsigned int len,DWORD flags)
{ 
	TUnzip *unz = new TUnzip();
	ZRESULT lasterrorU = unz->Open(z,len,flags);
	if (lasterrorU!=ZR_OK) 
	{
		delete unz; 
		return 0;
	}
	return (HZIP)unz;
}

ZRESULT GetZipItem(HZIP hz, WIN32_FIND_DATA *ze)
{ 
	if (hz==0) 
	{
		return ZR_ARGS;
	}
	TUnzip *han = (TUnzip*)hz;
	return han->Get(ze);
}

ZRESULT FindZipItem(HZIP hz, const TCHAR *name, bool ic, WIN32_FIND_DATA *ze)
{ 
	if (hz==0) 
	{
		return ZR_ARGS;
	}
	TUnzip *han = (TUnzip*)hz;
	return han->Find(name, ic, ze);
}

ZRESULT UnzipItem(HZIP hz, void *dst, unsigned int len, DWORD flags)
{ 
	if (hz==0) 
	{
		return ZR_ARGS;
	}
	TUnzip *han = (TUnzip*)hz;
	return han->Unzip(dst, len, flags);
}

ZRESULT CloseZip(HZIP hz)
{ if (hz==0) {return ZR_ARGS;}
  TUnzip *han = (TUnzip*)hz;
	ZRESULT lasterrorU = han->Close();
  delete han;
  return lasterrorU;
}
